/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace DaveSexton.DocProject.Sandcastle
{
	public sealed class ReflectionDocument : IDisposable
	{
		#region Public Properties
		public string File
		{
			get
			{
				return file;
			}
		}

		public string DocumentationSetName
		{
			get
			{
				return documentationSetName;
			}
		}

		/// <summary>
		/// Gets the <see cref="ReflectionApiElement"/> that corresponds to the specified <paramref name="topicFileName"/>.
		/// </summary>
		/// <param name="topicFileName">Name of the file, without an extension, that corresponds to the <see cref="ReflectionApiElement"/>
		/// that is to be retrieved.</param>
		/// <returns><see cref="ReflectionApiElement"/> for the specified <paramref name="topicFileName"/>.</returns>
		public ReflectionApiElement this[string topicFileName]
		{
			get
			{
				return elements[topicFileName];
			}
		}

		public bool IsLoaded
		{
			get
			{
				return loaded;
			}
		}
		#endregion

		#region Private / Protected
		private bool loaded;
		private readonly string documentationSetName, file;
		private readonly XmlReader reader;
		private readonly Dictionary<string, ReflectionApiElement> elements = new Dictionary<string, ReflectionApiElement>(1024, StringComparer.OrdinalIgnoreCase);
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ReflectionDocument" /> class.
		/// </summary>
		[System.Diagnostics.DebuggerHidden]
		public ReflectionDocument(string documentationSetName, string file)
		{
			if (string.IsNullOrEmpty(documentationSetName))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "documentationSetName");

			if (string.IsNullOrEmpty(file))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "file");

			this.documentationSetName = documentationSetName;
			this.file = file;
			this.reader = XmlReader.Create(file);
		}
		#endregion

		#region Methods
		public void Load()
		{
			try
			{
				ReadToApiElements();

				ForEachApiElement(delegate(ReflectionApiElement element)
				{
					// NOTE: In testing, Sandcastle generated multiple <Module> topics for different source assemblies but
					// they all used the same file name (presumably because they all had the exact same topic ID).
					// So here the last parsed API wins when there are duplicates: 
					elements[System.IO.Path.GetFileNameWithoutExtension(element.File) ?? element.TopicId] = element;
				});
			}
			finally
			{
				loaded = true;
				reader.Close();
			}
		}

		public void ReadToApiElements()
		{
			reader.ReadStartElement("reflection");

			if (reader.IsStartElement("assemblies"))
			{
				reader.Skip();
				reader.Read();
			}

			reader.ReadStartElement("apis");
		}

		public void ReadAndWriteToApiElements(XmlWriter writer)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");

			while (reader.Read())
			{
				if (reader.IsStartElement("reflection"))
				{
					writer.WriteStartElement("reflection");
					writer.WriteAttributes(reader, true);
					reader.Read();
					break;
				}
			}

			if (reader.IsStartElement("assemblies"))
			{
				writer.WriteRaw(Environment.NewLine + "  ");
				writer.WriteNode(reader, true);
			}

			reader.ReadStartElement("apis");

			writer.WriteRaw(Environment.NewLine + "  ");
			writer.WriteStartElement("apis");
		}

		public void ForEachApiElement(Action<ReflectionApiElement> action)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			if (loaded)
			{
				foreach (ReflectionApiElement element in elements.Values)
					action(element);
			}
			else
			{
				do
				{
					if (reader.IsStartElement("api") && reader.Depth == 2)
					{
						using (XmlReader apiReader = reader.ReadSubtree())
						{
							action(new ReflectionApiElement(documentationSetName, apiReader));
						}
					}
				}
				while (reader.Read());
			}
		}

		public ReflectionApiElement TryGetElement(string topicFileName)
		{
			if (!string.IsNullOrEmpty(topicFileName) && elements.ContainsKey(topicFileName))
				return elements[topicFileName];
			else
				return null;
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="ReflectionDocument" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="ReflectionDocument" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~ReflectionDocument()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="ReflectionDocument" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		private void Dispose(bool disposing)
		{
			if (disposing && reader != null)
				reader.Close();
		}
		#endregion
	}
}
